/*
 * Este programa es software libre; usted puede redistribuirlo y/o modificarlo bajo los terminos
 * de la licencia "GNU General Public License" publicada por la Fundacion "Free Software Foundation".
 * Este programa se distribuye con la esperanza de que pueda ser util, pero SIN NINGUNA GARANTIA;
 * vea la licencia "GNU General Public License" para obtener mas informacion.
 */
package adalid.core;

import adalid.commons.bundles.Bundle;
import adalid.commons.enums.LoggingLevel;
import adalid.commons.util.ThrowableUtils;
import adalid.commons.velocity.Writer;
import adalid.core.annotations.ProjectModule;
import adalid.core.enums.DisplayFormat;
import adalid.core.enums.DisplayMode;
import adalid.core.exceptions.InstantiationRuntimeException;
import adalid.core.interfaces.Artifact;
import adalid.core.interfaces.Entity;
import adalid.core.interfaces.Expression;
import adalid.core.interfaces.PersistentEntity;
import adalid.core.wrappers.PersistentEntityWrapper;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * @author Jorge Campins
 */
public abstract class Project extends AbstractArtifact implements Comparable<Project> {

    // <editor-fold defaultstate="collapsed" desc="static fields">
    private final Logger logger = Logger.getLogger(Project.class);

    private static int _defaultMaxDepth = 1;

    private static int _defaultMaxRound = 0;

    private static boolean _verbose = false;

    private static Level _alertLevel = Level.WARN;

    private static Level _detailLevel = Level.OFF;

    private static Level _trackingLevel = Level.OFF;
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="static fields' public getters and setters">
    /**
     * @return the locale
     */
    public static Locale getLocale() {
        return Bundle.getLocale();
    }

    /**
     * @param locale the locale to set
     */
    public static void setLocale(Locale locale) {
        Bundle.setLocale(locale);
    }

    /**
     * @return the default max depth
     */
    public static int getDefaultMaxDepth() {
        return _defaultMaxDepth;
    }

    /**
     * @param depth the default max depth to set
     */
    public static void setDefaultMaxDepth(int depth) {
        _defaultMaxDepth = depth < 1 ? 1 : depth;
    }

    /**
     * @return the default max round
     */
    public static int getDefaultMaxRound() {
        return _defaultMaxRound;
    }

    /**
     * @param round the default max round to set
     */
    public static void setDefaultMaxRound(int round) {
        _defaultMaxRound = round < 0 ? 0 : round;
    }

    /**
     * @return the verbose
     */
    public static boolean isVerbose() {
        return _verbose;
    }

    /**
     * @param verbose the verbose indicator to set
     */
    public static void setVerbose(boolean verbose) {
        _verbose = verbose;
    }

    /**
     * @return the alert messages logging level
     */
    public static Level getAlertLevel() {
        return _alertLevel;
    }

    /**
     * @param level the alert messages logging level to set; WARN will be used if level is null; ERROR and FATAL are downgraded to WARN; OFF disables
     * alert messages logging
     */
    public static void setAlertLevel(Level level) {
        _alertLevel = check(level, Level.WARN, Level.WARN);
    }

    /**
     * @return the detail messages logging level
     */
    public static Level getDetailLevel() {
        return _detailLevel;
    }

    /**
     * @param level the detail messages logging level to set; TRACE will be used if level is null; WARN, ERROR and FATAL are downgraded to INFO; OFF
     * disables detail messages logging
     */
    public static void setDetailLevel(Level level) {
        _detailLevel = check(level, Level.OFF, Level.INFO);
    }

    /**
     * @return the tracking messages logging level
     */
    public static Level getTrackingLevel() {
        return _trackingLevel;
    }

    /**
     * @param level the tracking messages logging level to set; TRACE will be used if level is null; WARN, ERROR and FATAL are downgraded to INFO; OFF
     * disables tracking messages logging
     */
    public static void setTrackingLevel(Level level) {
        _trackingLevel = check(level, Level.OFF, Level.INFO);
    }

    /**
     * @return the alert messages logging logginglevel
     */
    public static LoggingLevel getAlertLoggingLevel() {
        return LoggingLevel.getLoggingLevel(_alertLevel);
    }

    /**
     * @param logginglevel the alert messages logging logginglevel to set; WARN will be used if logginglevel is null; ERROR and FATAL are downgraded
     * to WARN; OFF disables alert messages logging
     */
    public static void setAlertLoggingLevel(LoggingLevel logginglevel) {
        setAlertLevel(logginglevel.getLevel());
    }

    /**
     * @return the detail messages logging logginglevel
     */
    public static LoggingLevel getDetailLoggingLevel() {
        return LoggingLevel.getLoggingLevel(_detailLevel);
    }

    /**
     * @param logginglevel the detail messages logging logginglevel to set; TRACE will be used if logginglevel is null; WARN, ERROR and FATAL are
     * downgraded to INFO; OFF disables detail messages logging
     */
    public static void setDetailLoggingLevel(LoggingLevel logginglevel) {
        setDetailLevel(logginglevel.getLevel());
    }

    /**
     * @return the tracking messages logging logginglevel
     */
    public static LoggingLevel getTrackingLoggingLevel() {
        return LoggingLevel.getLoggingLevel(_trackingLevel);
    }

    /**
     * @param logginglevel the tracking messages logging logginglevel to set; TRACE will be used if logginglevel is null; WARN, ERROR and FATAL are
     * downgraded to INFO; OFF disables tracking messages logging
     */
    public static void setTrackingLoggingLevel(LoggingLevel logginglevel) {
        setTrackingLevel(logginglevel.getLevel());
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="static fields' private getters and setters">
    private static Level check(Level level, Level defaultLevel, Level maxLevel) {
        Level coalesced = coalesce(level, defaultLevel);
        return coalesced.equals(Level.OFF) ? coalesced : minimum(coalesced, maxLevel);
    }
//
//  private static Level maximum(Level level1, Level level2) {
//      Level l1 = coalesce(level1, Level.ALL);
//      Level l2 = coalesce(level2, Level.ALL);
//      return l1.isGreaterOrEqual(l2) ? l1 : l2;
//  }

    private static Level minimum(Level level1, Level level2) {
        Level l1 = coalesce(level1, Level.OFF);
        Level l2 = coalesce(level2, Level.OFF);
        return l1.isGreaterOrEqual(l2) ? l2 : l1;
    }

    private static Level coalesce(Level level1, Level level2) {
        return level1 == null ? level2 == null ? Level.ALL : level2 : level1;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="instance fields">
    private Project _master;

    private Parser _parser;

    private Writer _writer;

    private Map<String, EntityReference> _entityReferences = new LinkedHashMap<>();

    private Map<String, ProjectReference> _projectReferences = new LinkedHashMap<>();

    private Map<String, Display> _displays = new LinkedHashMap<>();

    private Class<? extends Entity> _userEntityClass;

    private Set<String> _singularPlatforms = new LinkedHashSet<>();

    private Set<String> _processingGroups = new TreeSet<>();

    private boolean _annotatedWithProjectModule;

    private boolean _menuModule;

    private boolean _roleModule;
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="instance fields' public getters and setters">
    /**
     * @return the master project if this is a nested project; null otherwise
     */
    public Project getMaster() {
        return _master;
    }

    /**
     * @param master the master project to set
     */
    private void setMaster(Project master) {
        _master = master;
    }

    /**
     * @return the root entity of the specified class
     */
    public Entity getEntity(Class<?> type) {
        Class<?> clazz = XS1.getNamedClass(type);
        return getEntitiesMap().get(clazz.getSimpleName());
    }

    /**
     * @return the entities
     */
    public List<Entity> getEntitiesList() {
        List<Entity> list = new ArrayList<>();
        for (EntityReference reference : _entityReferences.values()) {
            if (reference.getEntity() != null) {
                list.add(reference.getEntity());
            }
        }
        return list;
    }

    /**
     * @return the entities
     */
    public Map<String, Entity> getEntitiesMap() {
        Map<String, Entity> entities = new LinkedHashMap<>();
        for (EntityReference reference : _entityReferences.values()) {
            if (reference.getEntity() != null) {
                entities.put(reference.getEntityClass().getSimpleName(), reference.getEntity());
            }
        }
        return entities;
    }

    /**
     * @return the root module of the specified class
     */
    public Project getModule(Class<?> type) {
        Project module = getProject(type);
        return module != null && module.getMaster() != null ? module : null;
    }

    /**
     * @return the modules
     */
    public List<Project> getModulesList() {
        List<Project> list = new ArrayList<>();
        for (ProjectReference reference : _projectReferences.values()) {
            Project module = reference.getProject();
            if (module != null && module.getMaster() != null) {
                list.add(module);
            }
        }
        return list;
    }

    /**
     * @return the modules
     */
    public Map<String, Project> getModulesMap() {
        Map<String, Project> projects = new LinkedHashMap<>();
        for (ProjectReference reference : _projectReferences.values()) {
            Project module = reference.getProject();
            if (module != null && module.getMaster() != null) {
                projects.put(reference.getProjectClass().getName(), module);
            }
        }
        return projects;
    }

    /**
     * @return the root project of the specified class
     */
    public Project getProject(Class<?> type) {
        Class<?> clazz = XS1.getNamedClass(type);
        return getProjectsMap().get(clazz.getName());
    }

    /**
     * @return the projects
     */
    public List<Project> getProjectsList() {
        List<Project> list = new ArrayList<>();
        for (ProjectReference reference : _projectReferences.values()) {
            if (reference.getProject() != null) {
                list.add(reference.getProject());
            }
        }
        return list;
    }

    /**
     * @return the projects
     */
    public Map<String, Project> getProjectsMap() {
        Map<String, Project> projects = new LinkedHashMap<>();
        for (ProjectReference reference : _projectReferences.values()) {
            if (reference.getProject() != null) {
                projects.put(reference.getProjectClass().getName(), reference.getProject());
            }
        }
        return projects;
    }

    /**
     * @return the displays list
     */
    public List<? extends Display> getDisplaysList() {
        return new ArrayList<>(getDisplaysMap().values());
    }

    /**
     * @return the displays map
     */
    public Map<String, ? extends Display> getDisplaysMap() {
        return _displays;
    }

    Set<String> crossReferencedExpressionsSet;

    /**
     * @return the cross-referenced expressions
     */
    public Set<String> getCrossReferencedExpressionsSet() {
        if (crossReferencedExpressionsSet == null) {
            crossReferencedExpressionsSet = new LinkedHashSet<>();
            PersistentEntity pent;
            List<Entity> entities = getEntitiesList();
            for (Entity entity : entities) {
                if (entity instanceof PersistentEntity) {
                    pent = (PersistentEntity) entity;
                    crossReferencedExpressionsSet.addAll(pent.getCrossReferencedExpressionsSet());
                }
            }
        }
        return crossReferencedExpressionsSet;
    }

    public boolean containsCrossReferencedExpression(Expression expression) {
        String key = expression == null ? null : expression.getCrossReferencedExpressionsKey();
        return key != null && getCrossReferencedExpressionsSet().contains(key);
    }

    Set<String> schemasSet;

    public Set<String> getSchemasSet() {
        String schema;
        if (schemasSet == null) {
            schemasSet = new LinkedHashSet<>();
            PersistentEntity pent;
            List<Entity> entities = getEntitiesList();
            for (Entity entity : entities) {
                if (entity instanceof PersistentEntity) {
                    pent = (PersistentEntity) entity;
                    schema = pent.getSchema();
                    if (StringUtils.isNotBlank(schema)) {
                        schemasSet.add(schema.trim());
                    }
                }
            }
        }
        return schemasSet;
    }

    Map<String, String> tablesMap;

    public Map<String, String> getTablesMap() {
        String table;
        if (tablesMap == null) {
            tablesMap = new LinkedHashMap<>();
            PersistentEntity pent;
            PersistentEntityWrapper wrapper;
            List<Entity> entities = getEntitiesList();
            for (Entity entity : entities) {
                if (entity instanceof PersistentEntity) {
                    pent = (PersistentEntity) entity;
                    wrapper = new PersistentEntityWrapper(pent);
                    table = wrapper.getSqlName();
                    if (StringUtils.isNotBlank(table)) {
                        tablesMap.put(table.trim(), pent.getClass().getName());
                    }
                }
            }
        }
        return tablesMap;
    }

    /**
     * @return the ProjectModule annotation indicator
     */
    public boolean isAnnotatedWithModule() {
        return _annotatedWithProjectModule;
    }

    /**
     * @return the menu module indicator
     */
    public boolean isMenuModule() {
        return _menuModule;
    }

    /**
     * @return the role module indicator
     */
    public boolean isRoleModule() {
        return _roleModule;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="instance fields' package getters and setters">
    Parser getParser() {
        if (_parser == null) {
            _parser = new Parser();
        }
        return _parser;
    }

    Writer getWriter() {
        if (_writer == null) {
            _writer = new Writer(this, "project");
        }
        return _writer;
    }

    /**
     * @return the entity reference of the specified class
     */
    EntityReference getEntityReference(Class<?> type) {
        Class<?> clazz = XS1.getNamedClass(type);
        return _entityReferences.get(clazz.getSimpleName());
    }

    /**
     * @return the true type of the specified class
     */
    Class<?> getTrueType(Class<?> type) {
        Class<?> clazz = XS1.getNamedClass(type);
        String key = clazz.getSimpleName();
        if (_entityReferences.containsKey(key)) {
            EntityReference reference = _entityReferences.get(key);
            return reference == null ? null : reference.getEntityClass();
        }
        return type;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="instance fields' private getters and setters">
    /**
     * @return the entity references map
     */
    private Map<String, EntityReference> getEntityReferences() {
        return _entityReferences;
    }

    /**
     * @return the project references map
     */
    private Map<String, ProjectReference> getProjectReferences() {
        return _projectReferences;
    }

    /**
     * @return the user entity class
     */
    public Class<? extends Entity> getUserEntityClass() {
        return _userEntityClass;
    }

    /**
     * @param clazz the user entity class to set
     */
    public void setUserEntityClass(Class<? extends Entity> clazz) {
        _userEntityClass = clazz;
    }

    /**
     * @return the singular platforms set
     */
    public Set<String> getSingularPlatforms() {
        return _singularPlatforms;
    }

    /**
     * @return the processing groups set
     */
    public Set<String> getProcessingGroups() {
        return _processingGroups;
    }
    // </editor-fold>

    public Project() {
        Class<?> namedClass = getNamedClass();
        String className = namedClass.getSimpleName();
        setDeclared(className);
        settle();
    }

    private void settle() {
        settleAttributes();
    }

    protected void settleAttributes() {
    }

    // <editor-fold defaultstate="collapsed" desc="annotate">
    @Override
    void initializeAnnotations() {
        super.initializeAnnotations();
        _annotatedWithProjectModule = false;
        _menuModule = false;
        _roleModule = false;
    }

    @Override
    void annotate(Class<?> type) {
        super.annotate(type);
        if (type != null) {
            annotateModule(type);
        }
    }

    @Override
    void annotate(Field field) {
        super.annotate(field);
        if (field != null) {
            annotateModule(field);
        }
    }

    @Override
    protected List<Class<? extends Annotation>> getValidTypeAnnotations() {
        List<Class<? extends Annotation>> valid = super.getValidTypeAnnotations();
        valid.add(ProjectModule.class);
        return valid;
    }

    @Override
    protected List<Class<? extends Annotation>> getValidFieldAnnotations() {
        List<Class<? extends Annotation>> valid = super.getValidFieldAnnotations();
        valid.add(ProjectModule.class);
        return valid;
    }

    private void annotateModule(Class<?> type) {
        Class<?> annotatedClass = XS1.getAnnotatedClass(type, ProjectModule.class);
        if (annotatedClass != null) {
            ProjectModule annotation = annotatedClass.getAnnotation(ProjectModule.class);
            if (annotation != null) {
                _annotatedWithProjectModule = true;
                _menuModule = annotation.menu().toBoolean(_menuModule);
                _roleModule = annotation.role().toBoolean(_roleModule);
            }
        }
    }

    private void annotateModule(Field field) {
        _annotatedWithProjectModule = field.isAnnotationPresent(ProjectModule.class);
        if (_annotatedWithProjectModule) {
            ProjectModule annotation = field.getAnnotation(ProjectModule.class);
            _menuModule = annotation.menu().toBoolean(_menuModule);
            _roleModule = annotation.role().toBoolean(_roleModule);
        }
    }
    // </editor-fold>

    public boolean build() {
        logger.info(signature("build", getClass().getName()));
        TLC.setProject(Project.this);
        boolean built = parse() && analyze();
        return built;
    }

    public boolean parse() {
        logger.info(signature("parse", getClass().getName()));
        TLC.setProject(Project.this);
        return getParser().parse();
    }

    public boolean analyze() {
        logger.info(signature("analyze", getClass().getName()));
        TLC.setProject(Project.this);
        List<Project> modulesList = getModulesList();
        Collections.sort(modulesList);
        boolean analyzed = true;
        String name;
        for (Project module : modulesList) {
            analyzed &= module.assemble();
            if (analyzed) {
                List<? extends Display> displaysList = module.getDisplaysList();
                for (Display display : displaysList) {
                    name = display.getName();
                    if (_displays.containsKey(name)) {
                    } else {
                        _displays.put(name, display);
                    }
                }
                continue;
            }
            break;
        }
        return analyzed;
    }

    protected boolean assemble() {
        log(_detailLevel, signature("assemble", getClass().getName()));
        return true;
    }

    public boolean generate() {
        logger.info(signature("generate", getClass().getName()));
        TLC.setProject(Project.this);
        configureWriter();
        return getWriter().write();
    }

    public boolean generate(String platform) {
        logger.info(signature("generate", "platform=" + platform));
        String alias = getAlias();
        if (StringUtils.isBlank(alias)) {
            logger.error("invalid project alias; generation aborted");
            return false;
        } else if (!alias.matches("^[a-z][a-z0-9]*$")) {
            logger.error(alias + " is an invalid project alias; generation aborted");
            return false;
        } else if (alias.equalsIgnoreCase("meta") || alias.equalsIgnoreCase("workspace")) {
            logger.error(alias + " is a restricted project alias; generation aborted");
            return false;
        } else if (_singularPlatforms.contains(platform) && platform.equals(alias)) {
            logger.error("project alias matches platform name; generation aborted");
            return false;
        }
        TLC.setProject(Project.this);
        configureWriter();
        return getWriter().write(platform);
    }

    private void configureWriter() {
        Writer.setAlertLevel(_alertLevel);
        Writer.setDetailLevel(_detailLevel);
        Writer.setTrackingLevel(_trackingLevel);
    }

    private String signature(String method, Object... parameters) {
        String pattern = "{0}({1})";
        return MessageFormat.format(pattern, method, StringUtils.join(parameters, ", "));
    }

    // <editor-fold defaultstate="collapsed" desc="entity display getters">
    public Display getReadingTableDisplayOf(Entity entity) {
        return getDisplayOf(entity, DisplayMode.READING, DisplayFormat.TABLE);
    }

    public Display getReadingDetailDisplayOf(Entity entity) {
        return getDisplayOf(entity, DisplayMode.READING, DisplayFormat.DETAIL);
    }

    public Display getReadingTreeDisplayOf(Entity entity) {
        return getDisplayOf(entity, DisplayMode.READING, DisplayFormat.TREE);
    }

    public Display getWritingTableDisplayOf(Entity entity) {
        return getDisplayOf(entity, DisplayMode.WRITING, DisplayFormat.TABLE);
    }

    public Display getWritingDetailDisplayOf(Entity entity) {
        return getDisplayOf(entity, DisplayMode.WRITING, DisplayFormat.DETAIL);
    }

    public Display getWritingTreeDisplayOf(Entity entity) {
        return getDisplayOf(entity, DisplayMode.WRITING, DisplayFormat.TREE);
    }

    public Display getProcessingConsoleDisplayOf(Entity entity) {
        return getDisplayOf(entity, DisplayMode.PROCESSING, DisplayFormat.CONSOLE);
    }

    private Display getDisplayOf(Entity entity, DisplayMode mode, DisplayFormat format) {
        if (entity == null) {
            return null;
        }
        Entity displayEntity;
        Entity displayMaster;
        DisplayMode displayMode;
        DisplayFormat displayFormat;
        List<? extends Display> displays = getDisplaysList();
        for (Display display : displays) {
            displayEntity = display.getEntity();
            displayMaster = display.getMaster();
            displayMode = display.getDisplayMode();
            displayFormat = display.getDisplayFormat();
            if (entity.equals(displayEntity) && displayMaster == null && mode.equals(displayMode) && format.equals(displayFormat)) {
                logger.debug(entity.getName() + " " + mode + " " + format + " display is " + display.getName() + " @ " + getName());
                return display;
            }
        }
        logger.debug(entity.getName() + " " + mode + " " + format + " display is not @ " + getName());
        return null;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="sibling display getters">
    public Display getTableSiblingOf(Display display) {
        return getSiblingOf(display, DisplayFormat.TABLE);
    }

    public Display getDetailSiblingOf(Display display) {
        return getSiblingOf(display, DisplayFormat.DETAIL);
    }

    public Display getTreeSiblingOf(Display display) {
        return getSiblingOf(display, DisplayFormat.TREE);
    }

    public Display getConsoleSiblingOf(Display display) {
        return getSiblingOf(display, DisplayFormat.CONSOLE);
    }

    private Display getSiblingOf(Display display, DisplayFormat format) {
        if (display == null) {
            return null;
        }
        Entity displayEntity = display.getEntity();
        Entity displayMaster = display.getMaster();
        if (displayEntity == null) {
            return null;
        }
        DisplayMode displayMode = display.getDisplayMode();
        DisplayFormat displayFormat = display.getDisplayFormat();
        if (displayMode == null || displayFormat == null) {
            return null;
        }
        DisplayMode mode = DisplayFormat.CONSOLE.equals(format) ? DisplayMode.PROCESSING
            : DisplayFormat.CONSOLE.equals(displayFormat) ? DisplayMode.UNSPECIFIED
            : displayMode;
        /**/
        Entity siblingEntity;
        Entity siblingMaster;
        DisplayMode siblingMode;
        DisplayFormat siblingFormat;
        List<? extends Display> siblings = getDisplaysList();
        for (Display sibling : siblings) {
            if (sibling.equals(display)) {
                continue;
            }
            siblingEntity = sibling.getEntity();
            siblingMaster = sibling.getMaster();
            siblingMode = sibling.getDisplayMode();
            siblingFormat = sibling.getDisplayFormat();
            if (displayEntity.equals(siblingEntity)) {
                if (format.equals(DisplayFormat.UNSPECIFIED) || format.equals(siblingFormat)) {
                    if (mode.equals(DisplayMode.UNSPECIFIED) || mode.equals(siblingMode)) {
                        if (siblingMaster == null && displayMaster == null) {
                            logger.debug(display.getName() + " " + format + " sibling is " + sibling.getName() + " @ " + getName());
                            return sibling;
                        }
                        if (siblingMaster != null && siblingMaster.equals(displayMaster)) {
                            logger.debug(display.getName() + " " + format + " sibling is " + sibling.getName() + " @ " + getName());
                            return sibling;
                        }
                    }
                }
            }
        }
        logger.debug(display.getName() + " " + format + " sibling is not @ " + getName());
        return null;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="cousin display getters">
    public Display getTableCousinOf(Display display) {
        return getCousinOf(display, DisplayFormat.TABLE);
    }

    public Display getDetailCousinOf(Display display) {
        return getCousinOf(display, DisplayFormat.DETAIL);
    }

    public Display getTreeCousinOf(Display display) {
        return getCousinOf(display, DisplayFormat.TREE);
    }

    public Display getConsoleCousinOf(Display display) {
        return getCousinOf(display, DisplayFormat.CONSOLE);
    }

    private Display getCousinOf(Display display, DisplayFormat format) {
        if (display == null) {
            return null;
        }
        Entity displayEntity = display.getEntity();
//      Entity displayMaster = display.getMaster();
        if (displayEntity == null) {
            return null;
        }
        DisplayMode displayMode = display.getDisplayMode();
        DisplayFormat displayFormat = display.getDisplayFormat();
        if (displayMode == null || displayFormat == null) {
            return null;
        }
        DisplayMode mode = DisplayFormat.CONSOLE.equals(format) ? DisplayMode.PROCESSING
            : DisplayFormat.CONSOLE.equals(displayFormat) ? DisplayMode.UNSPECIFIED
            : displayMode;
        /**/
        Entity cousinEntity;
        Entity cousinMaster;
        DisplayMode cousinMode;
        DisplayFormat cousinFormat;
        List<? extends Display> cousins = getDisplaysList();
        for (Display cousin : cousins) {
            if (cousin.equals(display)) {
                continue;
            }
            cousinEntity = cousin.getEntity();
            cousinMaster = cousin.getMaster();
            cousinMode = cousin.getDisplayMode();
            cousinFormat = cousin.getDisplayFormat();
            if (displayEntity.equals(cousinEntity)) {
                if (format.equals(DisplayFormat.UNSPECIFIED) || format.equals(cousinFormat)) {
                    if (mode.equals(DisplayMode.UNSPECIFIED) || mode.equals(cousinMode)) {
                        if (cousinMaster == null) {
                            logger.debug(display.getName() + " " + format + " cousin is " + cousin.getName() + " @ " + getName());
                            return cousin;
                        }
                    }
                }
            }
        }
        logger.debug(display.getName() + " " + format + " cousin is not @ " + getName());
        return null;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="reference display getters">
    public Display getReadingTableAlternativeTo(Display display, Entity entity) {
        return getAlternativeTo(display, entity, DisplayFormat.TABLE, DisplayMode.READING);
    }

    public Display getReadingDetailAlternativeTo(Display display, Entity entity) {
        return getAlternativeTo(display, entity, DisplayFormat.DETAIL, DisplayMode.READING);
    }

    public Display getReadingTreeAlternativeTo(Display display, Entity entity) {
        return getAlternativeTo(display, entity, DisplayFormat.TREE, DisplayMode.READING);
    }

    public Display getWritingTableAlternativeTo(Display display, Entity entity) {
        return getAlternativeTo(display, entity, DisplayFormat.TABLE, DisplayMode.WRITING);
    }

    public Display getWritingDetailAlternativeTo(Display display, Entity entity) {
        return getAlternativeTo(display, entity, DisplayFormat.DETAIL, DisplayMode.WRITING);
    }

    public Display getWritingTreeAlternativeTo(Display display, Entity entity) {
        return getAlternativeTo(display, entity, DisplayFormat.TREE, DisplayMode.WRITING);
    }

    public Display getProcessingConsoleAlternativeTo(Display display, Entity entity) {
        return getAlternativeTo(display, entity, DisplayFormat.CONSOLE, DisplayMode.PROCESSING);
    }

    private Display getAlternativeTo(Display display, Entity entity, DisplayFormat format, DisplayMode mode) {
        if (display == null || entity == null) {
            return null;
        }
        String name = display.getName() + "/" + entity.getName();
        Entity otherEntity;
        Entity otherMaster;
        DisplayMode otherMode;
        DisplayFormat otherFormat;
        List<? extends Display> others = getDisplaysList();
        for (Display other : others) {
            if (other.equals(display)) {
                continue;
            }
            otherEntity = other.getEntity();
            otherMaster = other.getMaster();
            otherMode = other.getDisplayMode();
            otherFormat = other.getDisplayFormat();
            if (entity.equals(otherEntity) && otherMaster == null && format.equals(otherFormat) && mode.equals(otherMode)) {
                logger.debug(name + " " + format + " alternative is " + other.getName() + " @ " + getName());
                return other;
            }
        }
        logger.debug(name + " " + format + " alternative is not @ " + getName());
        return null;
    }
    // </editor-fold>

    private void log(Level level, String method, Object... parameters) {
        if (foul(logger, level)) {
            return;
        }
        String message = signature(method, parameters);
        logger.log(level, message);
    }

    private boolean fair(Logger logger, Level level) {
        return logger != null && level != null && logger.isEnabledFor(level) && !level.equals(Level.OFF);
    }

    private boolean foul(Logger logger, Level level) {
        return !fair(logger, level);
    }

    // <editor-fold defaultstate="collapsed" desc="Comparable">
    @Override
    public int compareTo(Project o) {
        Project that;
        if (o != null) {
            that = o;
            String thisName = StringUtils.trimToEmpty(this.getName());
            String thatName = StringUtils.trimToEmpty(that.getName());
            return thisName.compareTo(thatName);
        }
        return 0;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="toString">
    @Override
    public String toString() {
        String str1 = getName();
        String str2 = getNamedClass().getSimpleName();
        String str3 = getAlias();
        String str4 = str1 == null || str1.equals(str2) ? str2 : str2 + "[" + str1 + "]";
        String str5 = str3 == null || str3.equals(str1) ? str4 : str4 + "[" + str3 + "]";
        String str6 = str5.replace("][", ", ");
        return str6 + "@" + Integer.toHexString(hashCode());
    }

    @Override
    protected String fieldsToString(int n, String key, boolean verbose, boolean fields, boolean maps) {
        String tab = verbose ? StringUtils.repeat(" ", 4) : "";
        String fee = verbose ? StringUtils.repeat(tab, n) : "";
        String faa = " = ";
        String foo = verbose ? EOL : ", ";
        String string = super.fieldsToString(n, key, verbose, fields, maps);
        if (fields || verbose) {
            string += fee + tab + "entities" + faa + _entityReferences.size() + foo;
            string += fee + tab + "projects" + faa + _projectReferences.size() + foo;
        }
        return string;
    }

    @Override
    protected String mapsToString(int n, String key, boolean verbose, boolean fields, boolean maps) {
        String string = super.mapsToString(n, key, verbose, fields, maps);
        if (maps || verbose) {
            for (String clave : _entityReferences.keySet()) {
                EntityReference valor = _entityReferences.get(clave);
                if (valor.getEntity() != null) {
                    string += valor.getEntity().toString(n + 1, clave, false, fields, false);
                }
            }
            for (String clave : _projectReferences.keySet()) {
                ProjectReference valor = _projectReferences.get(clave);
                if (valor.getProject() != null && valor.getProject() != this) {
                    string += valor.getProject().toString(n + 1, clave, false, fields, maps);
                }
            }
        }
        return string;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Parser">
    class Parser {

        private final Logger logger = Logger.getLogger(Project.Parser.class);

        private int maxDepthReached = 0;

        private int maxRoundReached = 0;

        private int entities = 0;

        private int warnings = 0;

        private int errors = 0;

        private boolean parse() {
            log(_detailLevel, "parse");
            logJavaClassPath();
            resetCounters();
            try {
                printSettings();
                putReferences();
                printProjectSummary(Level.INFO);
                printProjectReferencesSummary(Level.INFO);
                printEntityReferencesSummary(Level.INFO);
                if (errors == 0) {
                    initialiseEntityReferences();
                }
                if (errors == 0) {
                    settleEntityReferences();
                }
                if (errors == 0) {
                    finaliseEntityReferences();
                }
                if (errors == 0) {
                    checkEntityReferences();
                }
                if (errors == 0) {
                    initialiseProjectReferences();
                }
                printSummary();
                if (_verbose) {
                    printProjectReferencesDetail(_detailLevel);
                    printEntityReferencesDetail(_detailLevel);
                }
            } catch (Throwable throwable) {
                fatal(throwable);
            }
            return errors == 0;
        }

        private void logJavaClassPath() {
            String key = "java.class.path";
            String jcp = System.getProperty(key);
            if (jcp != null) {
//              logger.debug(key + "=" + EOL + jcp.replace(";", EOL));
                logger.debug(key);
                String[] strings = StringUtils.splitByWholeSeparator(jcp, ";");
                for (String string : strings) {
                    logger.debug(TAB + string);
                }
            }
        }

        // <editor-fold defaultstate="collapsed" desc="put references">
        private void putReferences() {
            log(_trackingLevel, "putReferences");
            Class<?> type = Project.this.getClass();
            Class<?> clazz = XS1.getNamedClass(type);
            Class<?> declaringType = null;
            putReferences(type, declaringType, Project.this, null);
            String key = clazz.getName();
            ProjectReference reference = _projectReferences.get(key);
            reference.setProject(Project.this);
        }

        private void putReferences(Class<?> type, Class<?> declaringType, Artifact declaringArtifact, Field declaringField) {
            String pattern;
            String remarks;
            Class<?> clazz = XS1.getNamedClass(type);
            int modifiers = clazz.getModifiers();
            boolean restricted = clazz.isPrimitive() || Modifier.isAbstract(modifiers) || !Modifier.isPublic(modifiers);
            if (restricted) {
            } else if (Entity.class.isAssignableFrom(clazz)) {
                String key = clazz.getSimpleName();
                if (_entityReferences.containsKey(key)) {
                    EntityReference reference = _entityReferences.get(key);
                    Class<?> entityClass = reference.getEntityClass();
                    if (clazz.equals(entityClass)) {
                        reference.putDeclaringType(declaringType);
                        reference.setExplicit(declaringType);
                        reference.setImplicit(declaringType);
                    } else if (clazz.isAssignableFrom(entityClass)) {
                        reference.putDeclaringType(declaringType);
                        reference.setExplicit(declaringType);
                        reference.setImplicit(declaringType);
                        logEntityReferenceOverride(_alertLevel, entityClass, clazz, declaringType, null);
                        warnings++;
                    } else if (entityClass.isAssignableFrom(clazz)) {
                        putEntityReference(clazz, declaringType, reference);
                        logEntityReferenceOverride(_alertLevel, clazz, entityClass, declaringType, null);
                        warnings++;
                    } else {
                        pattern = "{0} is not assignable from {1}";
                        remarks = MessageFormat.format(pattern, entityClass.getName(), clazz.getName());
                        logEntityReferenceOverride(Level.ERROR, clazz, entityClass, declaringType, remarks);
                        errors++;
                    }
                } else {
                    putEntityReference(clazz, declaringType);
                }
            } else if (Project.class.isAssignableFrom(clazz)) {
                if (declaringType == null || Project.class.isAssignableFrom(declaringType)) {
                    String key = clazz.getName();
                    if (_projectReferences.containsKey(key)) {
                        ProjectReference reference = _projectReferences.get(key);
                        reference.putDeclaringType(declaringType);
                    } else {
                        putProjectReference(clazz, declaringType, declaringArtifact, declaringField);
                    }
                }
            }
        }

        /**
         * @param riding = overriding class
         * @param ridden = overridden class
         * @param declaring = declaring class
         */
        private void logEntityReferenceOverride(Level level, Class<?> riding, Class<?> ridden, Class<?> declaring, String remarks) {
            if (foul(logger, level)) {
                return;
            }
            String pattern = level.isGreaterOrEqual(Level.ERROR) ? "failed to override" : "overriding";
            pattern += " reference to entity {0} at {1}";
            String name = riding.getSimpleName();
            String message = MessageFormat.format(pattern, name, typeTitleAndName(declaring));
            logger.log(level, message);
            //
            Level detailLevel = level.isGreaterOrEqual(Level.WARN) ? level : _detailLevel;
            if (foul(logger, detailLevel)) {
                return;
            }
            logger.log(detailLevel, TAB + "overriding class: " + riding.getName());
            logger.log(detailLevel, TAB + "overridden class: " + ridden.getName());
            if (StringUtils.isNotBlank(remarks)) {
                logger.log(detailLevel, TAB + remarks);
            }
        }

        private void putEntityReference(Class<?> type, Class<?> declaringType) {
            EntityReference previousReference = null;
            putEntityReference(type, declaringType, previousReference);
        }

        private void putEntityReference(Class<?> type, Class<?> declaringType, EntityReference previousReference) {
            String key = type.getSimpleName();
            Class<?> concreteSuperclass = XS1.getConcreteSuperclass(type);
            if (concreteSuperclass != null) {
                putReferences(concreteSuperclass, type, null, null);
            }
            boolean explicit = previousReference != null && previousReference.isExplicit();
            boolean implicit = previousReference != null && previousReference.isImplicit();
            EntityReference reference = new EntityReference(type);
            reference.putDeclaringType(declaringType);
            reference.setExplicit(explicit);
            reference.setExplicit(declaringType);
            reference.setImplicit(implicit);
            reference.setImplicit(declaringType);
            _entityReferences.put(key, reference);
            for (Field field1 : XS1.getFields(type, Entity.class)) { // type.getDeclaredFields()
                Class<?> fieldType1 = field1.getType();
                if (Entity.class.isAssignableFrom(fieldType1)) {
                    putReferences(fieldType1, type, null, null);
                } else if (Operation.class.isAssignableFrom(fieldType1)) {
                    for (Field field2 : XS1.getFields(fieldType1, Operation.class)) { // type.getDeclaredFields()
                        Class<?> fieldType2 = field2.getType();
                        if (Entity.class.isAssignableFrom(fieldType2)) {
                            putReferences(fieldType2, type, null, null);
                        }
                    }
                }
            }
        }

        private void putProjectReference(Class<?> type, Class<?> declaringType, Artifact declaringArtifact, Field declaringField) {
            String key = type.getName();
            ProjectReference reference = new ProjectReference(type);
            reference.putDeclaringType(declaringType);
            reference.setDeclaringArtifact(declaringArtifact);
            reference.setDeclaringField(declaringField);
            _projectReferences.put(key, reference);
            for (Field field : XS1.getFields(type, Project.class)) { // type.getDeclaredFields()
                putReferences(field.getType(), type, null, field);
            }
        }
        // </editor-fold>

        // <editor-fold defaultstate="collapsed" desc="initialise, settle and finalise entity references">
        private void initialiseEntityReferences() {
            log(_trackingLevel, "initialiseEntityReferences");
            Entity entity;
            for (EntityReference reference : _entityReferences.values()) {
                if (reference.getEntity() == null) {
//                  Entity entity = (Entity) reference.getEntityClass().newInstance();
                    entity = getEntityInstance(reference.getEntityClass());
                    reference.setEntity(entity);
                }
            }
            for (EntityReference reference : _entityReferences.values()) {
                entity = reference.getEntity();
                if (entity != null) {
                    entity.initialise();
                }
            }
        }

        private void settleEntityReferences() {
            log(_trackingLevel, "settleEntityReferences");
            Entity entity;
            for (EntityReference reference : _entityReferences.values()) {
                entity = reference.getEntity();
                if (entity != null) {
                    entity.settle();
                }
            }
        }

        private void finaliseEntityReferences() {
            log(_trackingLevel, "finaliseEntityReferences");
            Entity entity;
            for (EntityReference reference : _entityReferences.values()) {
                entity = reference.getEntity();
                if (entity != null) {
                    entity.finalise();
                }
            }
        }

        private void checkEntityReferences() {
            log(_trackingLevel, "checkEntityReferences");
            Entity entity;
            List<Entity> extensionsList;
            boolean concreteless;
            for (EntityReference reference : _entityReferences.values()) {
                entity = reference.getEntity();
                if (entity != null) {
                    if (entity.isAbstractClass()) {
                        concreteless = true;
                        extensionsList = entity.getExtensionsList();
                        for (Entity extension : extensionsList) {
                            if (extension.isAbstractClass()) {
                                continue;
                            }
                            concreteless = false;
                            break;
                        }
                        if (concreteless) {
                            logger.error(entity.getName() + " is an abstract class without concrete extensions");
                            increaseErrorCount();
                        }
                    }
                }
            }
        }

        private Entity getEntityInstance(Class<?> type) {
            String errmsg = "failed to create a new instance of " + type;
            try {
                return (Entity) type.getConstructor(Artifact.class, Field.class).newInstance(Project.this, null);
            } catch (IllegalArgumentException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
                throw new InstantiationRuntimeException(errmsg, ex);
            }
        }
        // </editor-fold>

        // <editor-fold defaultstate="collapsed" desc="initialise, settle and finalise project references">
        private void initialiseProjectReferences() {
            log(_trackingLevel, "initialiseProjectReferences");
            Project project;
            for (ProjectReference reference : _projectReferences.values()) {
                if (reference.getProject() == null) {
//                  Project project = (Project) reference.getProjectClass().newInstance();
                    project = getProjectInstance(reference.getProjectClass());
                    project.setMaster(Project.this);
                    project.resetDeclaringArtifact(reference.getDeclaringArtifact());
                    project.resetDeclaringField(reference.getDeclaringField());
//                  project.initializeAnnotations();
//                  project.annotate(project.getNamedClass());
                    project.annotate(reference.getDeclaringField());
                    reference.setProject(project);
                }
            }
            String key;
            EntityReference entityReference;
            ProjectReference projectReference;
            Map<String, Class<?>> declaredTypes;
            Map<String, EntityReference> entityReferences;
            Map<String, ProjectReference> projectReferences;
            for (ProjectReference reference : _projectReferences.values()) {
                project = reference.getProject();
                if (project != null && project != Project.this) {
                    entityReferences = project.getEntityReferences();
                    projectReferences = project.getProjectReferences();
                    declaredTypes = reference.getDeclaredTypes();
                    for (Class<?> declaredType : declaredTypes.values()) {
                        if (Entity.class.isAssignableFrom(declaredType)) {
                            key = declaredType.getSimpleName();
                            if (_entityReferences.containsKey(key)) {
                                entityReference = _entityReferences.get(key);
                                entityReferences.put(key, entityReference);
                            }
                        } else if (Project.class.isAssignableFrom(declaredType)) {
                            key = declaredType.getName();
                            if (_projectReferences.containsKey(key)) {
                                projectReference = _projectReferences.get(key);
                                projectReferences.put(key, projectReference);
                            }
                        }
                    }
                    project.getParser().printProjectSummary(_detailLevel);
                    project.getParser().printProjectReferencesSummary(_detailLevel);
                    project.getParser().printEntityReferencesSummary(_detailLevel);
                }
            }
        }

        private Project getProjectInstance(Class<?> type) {
            String errmsg = "failed to create a new instance of " + type;
            try {
                return (Project) type.newInstance();
            } catch (IllegalArgumentException | IllegalAccessException | InstantiationException | SecurityException ex) {
                throw new InstantiationRuntimeException(errmsg, ex);
            }
        }
        // </editor-fold>

        // <editor-fold defaultstate="collapsed" desc="print references">
        private void printEntityReferencesSummary(Level level) {
            if (foul(logger, level)) {
                return;
            }
            boolean explicit;
            boolean implicit;
            Map<String, Class<?>> references;
            Map<String, Class<?>> referenced;
            Entity e;
            String s;
            int i = _entityReferences.size();
            String pattern = i == 0
                ? "project {0} contains no references to entities"
                : "project {0} contains references to {1} distinct entities";
            String message = MessageFormat.format(pattern, Project.this.getClass().getName(), i);
            logger.log(level, message);
            //
            if (foul(logger, _detailLevel)) {
                return;
            }
            for (EntityReference reference : _entityReferences.values()) {
                explicit = reference.isExplicit();
                implicit = reference.isImplicit();
                references = reference.getDeclaredTypes();
                referenced = reference.getDeclaringTypes();
                e = reference.getEntity();
                s = reference.getEntityClass().getSimpleName();
                s += " {";
                s += reference.getEntityClass().getName();
                s += e == null ? "" : "@" + Integer.toHexString(e.hashCode());
                s += ", explicit=" + explicit;
                s += ", implicit=" + implicit;
                s += ", references=" + references.size();
                s += ", referenced=" + referenced.size();
                s += "} ";
                logger.log(_detailLevel, TAB + s);
                for (Class<?> declaredType : references.values()) {
                    logger.log(_detailLevel, TAB + TAB + "references " + typeTitleAndName(declaredType));
                }
                for (Class<?> declaringType : referenced.values()) {
                    logger.log(_detailLevel, TAB + TAB + "is referenced by " + typeTitleAndName(declaringType));
                }
            }
        }

        private void printEntityReferencesDetail(Level level) {
            if (foul(logger, level)) {
                return;
            }
            boolean initialised;
            String string;
            for (EntityReference reference : _entityReferences.values()) {
                initialised = reference.getEntity() != null;
                if (initialised) {
                    string = reference.getEntity().toString(0, null, _verbose, true, true);
                    logger.log(level, string);
                }
            }
        }

        private void printProjectSummary(Level level) {
            if (foul(logger, level)) {
                return;
            }
            String pattern;
            String message;
            if (_master == null) {
                pattern = "project {0} is the master project";
                message = MessageFormat.format(pattern, Project.this.getClass().getName());
            } else {
                pattern = "project {0} is nested within project {1}";
                message = MessageFormat.format(pattern, Project.this.getClass().getName(), _master.getClass().getName());
            }
            logger.log(level, message);
        }

        private void printProjectReferencesSummary(Level level) {
            if (foul(logger, level)) {
                return;
            }
            String pattern;
            String message;
            Map<String, Class<?>> references;
            Map<String, Class<?>> referenced;
            Project p;
            String s;
            Class<? extends Project> clazz = Project.this.getClass();
            int i = 0;
            for (ProjectReference reference : _projectReferences.values()) {
                if (!clazz.equals(reference.getProjectClass())) {
                    i++;
                }
            }
            pattern = i == 0
                ? "project {0} contains no references to other projects"
                : "project {0} contains references to {1} other projects";
            message = MessageFormat.format(pattern, Project.this.getClass().getName(), i);
            logger.log(level, message);
            //
            if (foul(logger, _detailLevel)) {
                return;
            }
            for (ProjectReference reference : _projectReferences.values()) {
                references = reference.getDeclaredTypes();
                referenced = reference.getDeclaringTypes();
                p = reference.getProject();
                s = reference.getProjectClass().getSimpleName();
                s += " {";
                s += reference.getProjectClass().getName();
                s += p == null ? "" : "@" + Integer.toHexString(p.hashCode());
                s += p == null ? "" : ", master=" + p.getMaster();
                s += ", references=" + references.size();
                s += ", referenced=" + referenced.size();
                s += "} ";
                logger.log(_detailLevel, TAB + s);
                for (Class<?> declaredType : references.values()) {
                    logger.log(_detailLevel, TAB + TAB + "references " + typeTitleAndName(declaredType));
                }
                for (Class<?> declaringType : referenced.values()) {
                    logger.log(_detailLevel, TAB + TAB + "is referenced by " + typeTitleAndName(declaringType));
                }
            }
        }

        private void printProjectReferencesDetail(Level level) {
            if (foul(logger, level)) {
                return;
            }
            boolean initialised;
            String string;
            for (ProjectReference reference : _projectReferences.values()) {
                initialised = reference.getProject() != null;
                if (initialised) {
                    string = reference.getProject().toString(0, null, _verbose, true, true);
                    logger.log(level, string);
                }
            }
        }
        // </editor-fold>

        private String typeTitleAndName(Class<?> type) {
            if (Entity.class.isAssignableFrom(type)) {
                return "entity " + type.getName();
            } else if (Project.class.isAssignableFrom(type)) {
                return "project " + type.getName();
            } else {
                return "" + type;
            }
        }

        private void printSettings() {
            logger.info("defaultMaxDepth=" + _defaultMaxDepth);
            logger.info("defaultMaxRound=" + _defaultMaxRound);
            logger.info("alertLevel=" + _alertLevel);
            logger.info("detailLevel=" + _detailLevel);
            logger.info("trackingLevel=" + _trackingLevel);
            logger.info("verbose=" + _verbose);
        }

        private void printSummary() {
            logger.info("maxDepthReached=" + maxDepthReached);
            logger.info("maxRoundReached=" + maxRoundReached);
            logger.info("entities=" + entities);
            if (warnings == 0) {
                logger.info("warnings=" + warnings);
            } else {
                logger.warn("warnings=" + warnings);
            }
            if (errors == 0) {
                logger.info("errors=" + errors);
            } else {
                logger.warn("errors=" + errors);
            }
        }

        private void resetCounters() {
            maxDepthReached = 0;
            maxRoundReached = 0;
            entities = 0;
            warnings = 0;
            errors = 0;
        }

        /**
         * @param depth the maxDepthReached to set
         */
        void setMaxDepthReached(int depth) {
            if (depth > maxDepthReached) {
                maxDepthReached = depth;
            }
        }

        /**
         * @param round the maxRoundReached to set
         */
        void setMaxRoundReached(int round) {
            if (round > maxRoundReached) {
                maxRoundReached = round;
            }
        }

        /**
         *
         */
        void increaseEntityCount() {
            entities++;
        }

        /**
         *
         */
        void increaseWarningCount() {
            warnings++;
        }

        /**
         *
         */
        void increaseErrorCount() {
            errors++;
        }

        void log(Level level, String message) {
            if (foul(logger, level)) {
                return;
            }
            logger.log(level, message);
        }

        private void log(Level level, String method, Object... parameters) {
            if (foul(logger, level)) {
                return;
            }
            String message = signature(method, parameters);
            logger.log(level, message);
        }

        private boolean fair(Logger logger, Level level) {
            return logger != null && level != null && logger.isEnabledFor(level) && !level.equals(Level.OFF);
        }

        private boolean foul(Logger logger, Level level) {
            return !fair(logger, level);
        }

        void warn(Object message) {
            logger.warn(message);
            warnings++;
        }

        void error(Object message) {
            logger.error(message);
            errors++;
        }

        /**
         * @see AbstractEntity
         * @see EntityFolderAtlas
         * @see Operation
         */
        void track(int depth, int round, String path, String method, Object... parameters) {
            track(_trackingLevel, depth, round, path, signature(method, parameters), null);
        }

        /**
         * @see XS1
         */
        void track(int depth, int round, String path, Class<?> type, String name, String method, String remarks) {
            String tipo = type == null ? "" : type.getSimpleName();
            String note = tipo + "[" + name + "]" + "." + method;
            track(_trackingLevel, depth, round, path, note, remarks);
        }

        /**
         * @see XS1
         */
        void alert(int depth, int round, String path, Class<?> type, String name, String method, String remarks) {
            String tipo = type == null ? "" : type.getSimpleName();
            String note = tipo + "[" + name + "]" + "." + method;
            track(_alertLevel, depth, round, path, note, remarks);
            warnings++;
        }

        private void track(Level level, int depth, int round, String path, String note, String remarks) {
            if (foul(logger, level)) {
                return;
            }
            boolean margin = fair(logger, _trackingLevel);
            String margin1 = margin ? StringUtils.repeat(" ", 5 * depth) : "";
            String margin2 = margin ? StringUtils.repeat(" ", 5 * (depth + 1)) : TAB;
            String message = margin1;
            message += "d=" + depth + ", r=" + round + ", ";
            if (StringUtils.isNotBlank(path)) {
                message += path + ".";
            }
            message += note;
            logger.log(level, message);
            if (StringUtils.isNotBlank(remarks)) {
                logger.log(level, margin2 + remarks);
            }
        }

        private void fatal(Throwable throwable) {
            Throwable cause = ThrowableUtils.getCause(throwable);
            String message = throwable.equals(cause) ? throwable.getClass().getSimpleName() : throwable.getMessage();
            logger.fatal(message, cause);
            errors++;
        }

    } // Parser
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="EntityReference">
    class EntityReference {

        private Entity _entity;

        private Class<?> _entityClass;

        private boolean _explicit;

        private boolean _implicit;

        private Map<String, Class<?>> _declaredTypes = new TreeMap<>();

        private Map<String, Class<?>> _declaringTypes = new TreeMap<>();

        /**
         * @return the entity
         */
        private Entity getEntity() {
            return _entity;
        }

        /**
         * @param entity the entity to set
         */
        private void setEntity(Entity entity) {
            _entity = entity;
        }

        /**
         * @return the entity class
         */
        private Class<?> getEntityClass() {
            return _entityClass;
        }

        private EntityReference() {
        }

        private EntityReference(Entity entity) {
            assert entity != null;
            _entity = entity;
            _entityClass = entity.getClass();
        }

        private EntityReference(Class<?> entityClass) {
            assert entityClass != null;
            _entity = null;
            _entityClass = entityClass;
        }

        private void putDeclaredType(Class<?> declaredType) {
//          if (declaredType == null) {
//          } else {
            _declaredTypes.put(declaredType.getName(), declaredType);
//          }
        }

        private Map<String, Class<?>> getDeclaredTypes() {
            return _declaredTypes;
        }

        private void putDeclaringType(Class<?> declaringType) {
            if (declaringType == null) {
            } else {
                _declaringTypes.put(declaringType.getName(), declaringType);
                if (Entity.class.isAssignableFrom(declaringType)) {
                    String key = declaringType.getSimpleName();
                    if (_entityReferences.containsKey(key)) {
                        EntityReference reference = _entityReferences.get(key);
                        reference.putDeclaredType(_entityClass);
                    }
                } else if (Project.class.isAssignableFrom(declaringType)) {
                    String key = declaringType.getName();
                    ProjectReference reference = _projectReferences.get(key);
                    reference.putDeclaredType(_entityClass);
                }
            }
        }

        private Map<String, Class<?>> getDeclaringTypes() {
            return _declaringTypes;
        }

        /**
         * @return the explicit reference indicator
         */
        boolean isExplicit() {
            return _explicit;
        }

        /**
         * @param explicit the explicit reference indicator to set
         */
        private void setExplicit(boolean explicit) {
            _explicit = explicit;
        }

        /**
         * @param explicit the explicit reference indicator to set
         */
        private void setExplicit(Class<?> declaringType) {
            _explicit |= Project.class.isAssignableFrom(declaringType);
        }

        /**
         * @return the implicit reference indicator
         */
        boolean isImplicit() {
            return _implicit;
        }

        /**
         * @param implicit the implicit reference indicator to set
         */
        private void setImplicit(boolean implicit) {
            _implicit = implicit;
        }

        /**
         * @param implicit the implicit reference indicator to set
         */
        private void setImplicit(Class<?> declaringType) {
            _implicit |= Entity.class.isAssignableFrom(declaringType);
        }

    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="ProjectReference">
    private class ProjectReference {

        private Project _project;

        private Class<?> _projectClass;

        private Artifact _declaringArtifact;

        private Field _declaringField;

        private Map<String, Class<?>> _declaredTypes = new TreeMap<>();

        private Map<String, Class<?>> _declaringTypes = new TreeMap<>();

        /**
         * @return the project
         */
        private Project getProject() {
            return _project;
        }

        /**
         * @param project the project to set
         */
        private void setProject(Project project) {
            _project = project;
        }

        /**
         * @return the project class
         */
        private Class<?> getProjectClass() {
            return _projectClass;
        }

        private ProjectReference() {
        }

        private ProjectReference(Project project) {
            assert project != null;
            _project = project;
            _projectClass = project.getClass();
        }

        private ProjectReference(Class<?> projectClass) {
            assert projectClass != null;
            _project = null;
            _projectClass = projectClass;
        }

        private void putDeclaredType(Class<?> declaredType) {
//          if (declaredType == null) {
//          } else {
            _declaredTypes.put(declaredType.getName(), declaredType);
//          }
        }

        private Map<String, Class<?>> getDeclaredTypes() {
            return _declaredTypes;
        }

        private void putDeclaringType(Class<?> declaringType) {
            if (declaringType == null) {
            } else {
                _declaringTypes.put(declaringType.getName(), declaringType);
                if (Project.class.isAssignableFrom(declaringType)) {
                    String key = declaringType.getName();
                    ProjectReference reference = _projectReferences.get(key);
                    reference.putDeclaredType(_projectClass);
                }
            }
        }

        private Map<String, Class<?>> getDeclaringTypes() {
            return _declaringTypes;
        }

        /**
         * @return the declaring artifact
         */
        private Artifact getDeclaringArtifact() {
            return _declaringArtifact;
        }

        /**
         * @param declaringArtifact the declaring artifact to set
         */
        private void setDeclaringArtifact(Artifact declaringArtifact) {
            _declaringArtifact = declaringArtifact;
        }

        /**
         * @return the declaring field
         */
        private Field getDeclaringField() {
            return _declaringField;
        }

        /**
         * @param declaringField the declaring field to set
         */
        private void setDeclaringField(Field declaringField) {
            _declaringField = declaringField;
        }

    }
    // </editor-fold>

}
